// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <google/protobuf/descriptor.h>
#include <krpc/utility/time.h>                      // gettimeofday_us
#include <krpc/rpc/server.h>                    // Server
#include <krpc/rpc/restful_service.h>
#include <turbo/strings/str_format.h>
#include <turbo/strings/str_split.h>

namespace krpc {
    static std::string normalize_path(const std::string &path) {
        std::string result;
        std::vector<std::string_view> segments = turbo::str_split(path, "/", turbo::SkipEmpty());
        for (const auto &seg: segments) {
            result.push_back('/');
            result.append(seg);
        }
        return result;
    }

    RestfulService::RestfulService() {
        auto root_func = [](const RestfulRequest *req, RestfulResponse *res) -> void {
            res->set_status_code(200);
            res->set_content_type("text/plain");
            res->append_body("kumo restful server!");
        };
        root_processor_ = std::make_shared<FunctionRestfulProcessor>(root_func);

        auto not_found_func = [](const RestfulRequest *req, RestfulResponse *res) -> void {
            res->set_status_code(404);
            res->set_content_type("text/plain");
            res->append_body("kumo not found method!");
        };
        not_found_processor_ = std::make_shared<FunctionRestfulProcessor>(not_found_func);
    }

    void RestfulService::default_method(::google::protobuf::RpcController *controller,
                                        const KMRestfulRequest *request,
                                        KMRestfulResponse *response,
                                        ::google::protobuf::Closure *done) {
        (void) request;
        (void) response;
        ClosureGuard done_guard(done);
        auto *ctrl = dynamic_cast<Controller *>(controller);
        const RestfulRequest req(ctrl);
        RestfulResponse resp(ctrl);
        root_processor_->process(&req, &resp);
    }

    void RestfulService::do_impl(::google::protobuf::RpcController *controller,
                                 const KMRestfulRequest *request,
                                 KMRestfulResponse *response,
                                 ::google::protobuf::Closure *done) {
        (void) request;
        (void) response;
        auto *ctrl = dynamic_cast<Controller *>(controller);
        const RestfulRequest req(ctrl);
        auto path = req.mapping_path();
        auto it = processors_.find(path);
        if (it == processors_.end()) {
            krpc::ClosureGuard done_guard(done);
            RestfulResponse resp(ctrl);
            not_found_processor_->process(&req, &resp);
            return;
        }
        auto processor = it->second.get();

        auto func = [processor, controller, done] {
            krpc::ClosureGuard done_guard(done);
            auto *ctrl = dynamic_cast<Controller *>(controller);
            const RestfulRequest req(ctrl);
            RestfulResponse resp(ctrl);
            processor->process(&req, &resp);
        };
        kthread::Fiber f;
        f.run(std::move(func));
        f.join();
    }

    turbo::Status RestfulService::register_server(Server *server) {
        registered_ = true;
        auto map_str = make_mapping_string();
        int r;
        if (map_str.empty()) {
            r = server->AddService(this,
                                   krpc::SERVER_DOESNT_OWN_SERVICE);
        } else {
            r = server->AddService(this,
                                   krpc::SERVER_DOESNT_OWN_SERVICE,
                                   map_str);
        }

        if (r != 0) {
            return turbo::internal_error("register restful service failed");
        }
        return turbo::OkStatus();
    }

    std::string RestfulService::make_mapping_string() {
        std::string result;
        bool first = true;
        for (auto it = processors_.begin(); it != processors_.end(); ++it) {
            if (!first) {
                result += ", ";
            }
            first = false;
            result += it->first;
            result += " => do_impl";
            if (it->second->is_wildcards()) {
                result += ", ";
                result += it->first + "/*";
                result += " => do_impl";
            }
        }
        return result;
    }

    RestfulService *RestfulService::set_not_found_processor(std::shared_ptr<RestfulProcessor> processor) {
        LOG_IF(FATAL, registered_) << "set_not_found_processor must be called before register_server";
        not_found_processor_ = std::move(processor);
        return this;
    }

    RestfulService *RestfulService::set_any_path_processor(std::shared_ptr<RestfulProcessor> processor) {
        LOG_IF(FATAL, registered_) << "set_not_found_processor must be called before register_server";
        any_path_processor_ = std::move(processor);
        return this;
    }

    RestfulService *RestfulService::set_root_processor(std::shared_ptr<RestfulProcessor> processor) {
        LOG_IF(FATAL, registered_) << "set_not_found_processor must be called before register_server";
        root_processor_ = std::move(processor);
        return this;
    }

    RestfulService *
    RestfulService::set_processor(const std::string &path, std::shared_ptr<RestfulProcessor> processor) {
        LOG_IF(FATAL, registered_) << "set_not_found_processor must be called before register_server";
        auto fpath = normalize_path(path);
        if (fpath == "/") {
            LOG(FATAL) << "path is empty";
        }
        auto it = processors_.find(fpath);
        if (it != processors_.end()) {
            LOG(FATAL) << "processor already exists for path: " << path;
        }
        processors_[fpath] = std::move(processor);
        return this;
    }

    RestfulService *RestfulService::set_processor(const std::string &path,
                                                  std::function<void(const RestfulRequest *, RestfulResponse *)> func,
                                                  bool is_wildcards) {
        LOG_IF(FATAL, registered_) << "set_not_found_processor must be called before register_server";
        auto fpath = normalize_path(path);
        if (fpath == "/") {
            LOG(FATAL) << "path is empty";
        }
        auto it = processors_.find(fpath);
        if (it != processors_.end()) {
            LOG(FATAL) << "processor already exists for path: " << path;
        }
        processors_[fpath] = std::make_shared<FunctionRestfulProcessor>(func, is_wildcards);
        return this;

    }

    RestfulService *RestfulService::set_mapping_path(const std::string &mapping_path) {
        LOG_IF(FATAL, registered_) << "set_not_found_processor must be called before register_server";
        mapping_path_ = mapping_path;
        return this;
    }
} // namespace krpc
